﻿using System;
using System.IO.Ports;
using System.Linq;
using System.Threading;

namespace MDK.Device.Components
{
    public interface IWeigher
    {
        double GetWeight();
        event CommunicationErrorHandler CommunicationError;
    }

    public class Weigher : IWeigher
    {
        private static readonly byte[] _cmdReadWeigh = { 0xAA, 0xAA, 0xAA, 0x01, 0xB1, 0x00, 0x00, 0x1A };

        private SerialPort _serialPort;

        public string SerialPortName { get; }

        public TimeSpan Timeout { get; set; }

        public Weigher(string com)
        {
            SerialPortName = com;
            Timeout = TimeSpan.FromSeconds(1);
        }

        public double GetWeight()
        {
            var retryCount = 0;
            var errorcode = 0;
            while (true)
            {
                if (_serialPort == null)
                {
                    if (!SerialPort.GetPortNames().Contains(SerialPortName))
                    {
                        errorcode = ErrorCodeHelper.SerialPortNotExist;
                    }
                    else
                    {
                        _serialPort = new SerialPort(SerialPortName);
                        try
                        {
                            _serialPort.Open();
                        }
                        catch (Exception)
                        {
                            errorcode = ErrorCodeHelper.SerialPortOccupied;
                            _serialPort.Close();
                            _serialPort = null;
                        }
                    }
                }

                if (errorcode == 0)
                {
                    try
                    {
                        _serialPort.DiscardInBuffer();
                        _serialPort.Write(_cmdReadWeigh, 0, _cmdReadWeigh.Length);
                        var len = 0;
                        var startTime = DateTime.Now;
                        var buf = new byte[7];
                        while (len < buf.Length)
                        {
                            Thread.Sleep(10);
                            if (_serialPort.BytesToRead > 0)
                            {
                                len += _serialPort.Read(buf, len, _serialPort.BytesToRead > buf.Length - len ? buf.Length - len : _serialPort.BytesToRead);
                            }

                            if (DateTime.Now - startTime > Timeout)
                            {
                                errorcode = ErrorCodeHelper.DeviceTimeout;
                            }
                        }

                        if (errorcode == 0)
                        {
                            return ((buf[5] << 8) + buf[6]);
                        }
                    }
                    catch (Exception)
                    {
                        errorcode = ErrorCodeHelper.SerialPortComError;
                        _serialPort.Close();
                        _serialPort = null;
                    }
                    
                }

                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    retryCount++;
                }
                else
                {
                    return double.NaN;
                }
            }
        }

        public event CommunicationErrorHandler CommunicationError;

        protected virtual bool OnCommunicationError(int errorcode, int retryCount)
        {
            var handler = CommunicationError;
            if (handler != null)
            {
                return handler.Invoke(this, errorcode, retryCount);
            }

            return false;
        }
    }
}